package com.xmlt.core.template.tags;

import java.util.List;
import java.util.Stack;

import ognl.OgnlException;

import com.xmlt.core.template.Template;
import com.xmlt.core.template.TemplateSection;
import com.xmlt.core.template.instance.AbstractInstance;
import com.xmlt.core.template.instance.Instance;
import com.xmlt.core.template.instance.MultiInstance;
import com.xmlt.core.template.instance.Value;

public abstract class AbstractSectionElementVisitor extends BaseTemplateSectionElementVisitor {
	private Stack<AbstractInstance> instanceStack;
	protected AbstractSectionElementVisitor(AbstractInstance inst) {
		instanceStack = new Stack<AbstractInstance>();
		instanceStack.push(inst);
	}
	
	protected final AbstractInstance getTopStackElement() {
		return instanceStack.peek();
	}
	
	@Override
	public final void visit(TextTag textTag) {
		handleTextTag(textTag);
	}

	@Override
	public final void visit(IfTag ifTag) {
		visitChildren(ifTag);
	}

	@Override
	public final void visit(LoopTag loopTag) {
		visitChildren(loopTag);
	}

	@Override
	public final void visit(OutputTag outputTag) {
		AbstractInstance instance = instanceStack.peek();
		Object value = null;
		try {
			value = outputTag.getValue(instance);
		} catch (Exception e) {
			handleException(e);
		}
		if (value instanceof AbstractInstance) {
			visitInstance((AbstractInstance) value);
		} else {
			handleObject(value);
		}
	}
	
	private void visitChildren(TemplateSectionElement tse) {
		try {
			List<TemplateSectionElement> tags = tse.generateTags(instanceStack.peek());
			for (TemplateSectionElement sectionElement: tags) {
				sectionElement.accept(this);
			}
		} catch (OgnlException e) {
			throw new RuntimeException(e);
		}
	}
	
	protected void visitInstance(AbstractInstance inst) {
		if (inst instanceof Value) {
			handleValue((Value) inst);
		} else if (inst instanceof MultiInstance) {
			MultiInstance multiInstance = (MultiInstance) inst;
			handleMultiInstanceStart(multiInstance);
			
			List<AbstractInstance> children = ((MultiInstance) inst).getChildren();
			for (int index = 0; index < children.size(); index++) {
				AbstractInstance child = children.get(index);
				handleMultiInstanceItemStart(child, multiInstance, index);
				visitInstance(child);
				handleMultiInstanceItemEnd(child, multiInstance, index);
			}
			
			handleMultiInstanceEnd(multiInstance);
		} else if (inst instanceof Instance) {
			Template template = ((Instance) inst).getTemplate();
			TemplateSection section = template.getDisplaySection();
			try {
				instanceStack.push(inst);
				section.accept(this);
			} finally {
				instanceStack.pop();
			}
		}
	}
	
	public abstract void handleException(Exception e);
	
	public abstract void handleObject(Object o);
	
	public abstract void handleTextTag(TextTag textTag);
	
	public abstract void handleValue(Value value);
	
	public abstract void handleMultiInstanceStart(MultiInstance multiInstance);
	
	public abstract void handleMultiInstanceEnd(MultiInstance multiInstance);
	
	public abstract void handleMultiInstanceItemStart(AbstractInstance itemInstance, MultiInstance multiInstance, int index);
	
	public abstract void handleMultiInstanceItemEnd(AbstractInstance itemInstance, MultiInstance multiInstance, int index);
}
